﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace Truncation.Detector.Win32
{
    // https://msdn.microsoft.com/en-us/library/windows/desktop/dd183499(v=vs.85).aspx
    internal class Font : IDisposable
    {
        private IntPtr _hFont = IntPtr.Zero;
        private IntPtr _hDc = IntPtr.Zero;
        private static readonly IntPtr hWnd = IntPtr.Zero;
        public Font(string name, int size, int weight = 400, uint itailc = 0, uint charset = 0)
        {
            _hDc = GetDC(hWnd);
            int lfHeight = -MulDiv(size, GetDeviceCaps(_hDc, 90), 72);
            _hFont = CreateFont(lfHeight, 0, 0, 0, weight, itailc, 0, 0, charset, 0, 0, 0, 0, name);
        }

        public bool Measure(string txt, int clientWidth, int clientHeight)
        {
            IntPtr handle = SelectObject(_hDc, _hFont);
            TextElementEnumerator charEnum = StringInfo.GetTextElementEnumerator(txt);
            int total = 0;
            int fontHeight = 16;//TextRenderer.MeasureText(txt, font).Height;
            if (clientHeight < fontHeight)
                return false;
            while (clientHeight > 0 && charEnum.MoveNext())
            {
                string textElement = charEnum.GetTextElement();

                //int unicode = textElement.Length == 2 ? Char.ConvertToUtf32(textElement[0], textElement[1]) : Char.ConvertToUtf32(textElement, 0);

                int unicode = textElement.Length == 2 ? (textElement[0] << 16 | textElement[1]) : textElement[0];

                int width = 0;
                if (unicode < 32 && unicode > 0)
                {
                    if (unicode == 0x0a)
                    {
                        clientHeight -= font.Height;
                    }
                }
                else if (textElement.Length == 1)
                {
                    ABC[] glyphSet = new ABC[1];
                    GetCharABCWidthsW(_hDc, (uint)unicode, (uint)unicode, glyphSet);
                    width = glyphSet[0].abcA + (int)glyphSet[0].abcB + glyphSet[0].abcC;
                }
                else
                {
                    var bmp = new Bitmap(100, 100);
                    var g = Graphics.FromImage(bmp);
                    width = TextWidth(g, font, textElement);
                    g.Dispose();
                    bmp.Dispose();
                }
                total += width;
                if (total > clientWidth)
                {
                    total = width;
                    clientHeight -= font.Height;
                }
            }
            if (clientHeight <= 0)
            {
                return false;
            }
            return true;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct ABC
        {
            public int abcA;
            public uint abcB;
            public int abcC;
        }

        [DllImport("gdi32.dll")]
        private static extern int GetCharABCWidthsW(IntPtr hdc, uint start, uint end, [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStruct)] ABC[] lpABC);

        [DllImport("gdi32.dll")]
        private static extern int DeleteObject(IntPtr hGdiObj);

        [DllImport("user32.dll")]
        private static extern IntPtr GetDC(IntPtr hWnd);

        [DllImport("user32.dll")]
        private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);

        [DllImport("gdi32.dll")]
        private static extern IntPtr SelectObject(IntPtr hDC, IntPtr hGdiObj);

        [DllImport("gdi32.dll")]
        static extern IntPtr CreateFont(int nHeight, int nWidth, int nEscapement,
           int nOrientation, int fnWeight, uint fdwItalic, uint fdwUnderline, uint
           fdwStrikeOut, uint fdwCharSet, uint fdwOutputPrecision, uint
           fdwClipPrecision, uint fdwQuality, uint fdwPitchAndFamily, string lpszFace);

        [DllImport("gdi32.dll")]
        static extern int GetDeviceCaps(IntPtr hdc, int nIndex);

        [DllImport("kernel32.dll")]
        static extern int MulDiv(int nNumber, int nNumerator, int nDenominator);

        public void Dispose()
        {
            if (_hFont != IntPtr.Zero)
            {
                DeleteObject(_hFont);
                _hFont = IntPtr.Zero;
            }
            
            if (_hDc != IntPtr.Zero)
            {
                ReleaseDC(hWnd, _hDc);
                _hDc = IntPtr.Zero;
            }
            
        }
    }
}
